/*
 * Copyright (C) 2020 Graylog, Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the Server Side Public License, version 1,
 * as published by MongoDB, Inc.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * Server Side Public License for more details.
 *
 * You should have received a copy of the Server Side Public License
 * along with this program. If not, see
 * <http://www.mongodb.com/licensing/server-side-public-license>.
 */
import userEvent from '@testing-library/user-event';
import * as React from 'react';
import { useContext } from 'react';
import { render, screen, waitFor } from 'wrappedTestingLibrary';
import * as Immutable from 'immutable';
import type { PluginRegistration } from 'graylog-web-plugin/plugin';
import { PluginStore } from 'graylog-web-plugin/plugin';

import selectEvent from 'helpers/selectEvent';
import AggregationWizard from 'views/components/aggregationwizard/AggregationWizard';
import AggregationWidgetConfig from 'views/logic/aggregationbuilder/AggregationWidgetConfig';
import { makeVisualization } from 'views/components/aggregationbuilder/AggregationBuilder';
import type VisualizationConfig from 'views/logic/aggregationbuilder/visualizations/VisualizationConfig';
import OnVisualizationConfigChangeContext from 'views/components/aggregationwizard/OnVisualizationConfigChangeContext';

const widgetConfig = AggregationWidgetConfig.builder().visualization('table').build();

const SimpleAggregationWizard = (props) => (
  <AggregationWizard
    config={widgetConfig}
    editing
    id="widget-id"
    type="AGGREGATION"
    fields={Immutable.List([])}
    onChange={() => {}}
    {...props}
  />
);

const dataTableVisualization = makeVisualization(() => <span>This is the chart.</span>, 'table');
const mapVisualization = makeVisualization(() => <span>This is the map.</span>, 'map');

interface ExtraConfigSettings {
  mode: 'onemode' | 'anothermode' | 'thirdmode';
  color?: 'red' | 'green' | 'blue';
  invert: boolean;
  factor: number;
}

interface ExtraConfigWidget extends VisualizationConfig, ExtraConfigSettings {}

const fromConfig = (config: ExtraConfigWidget): ExtraConfigSettings => ({ ...config });
const createVisualizationConfig = (config: ExtraConfigSettings) =>
  ({
    ...config,
  }) as ExtraConfigWidget;
const toConfig = (config: ExtraConfigSettings): ExtraConfigWidget => createVisualizationConfig(config);

const visualizationPlugin: PluginRegistration = {
  exports: {
    visualizationTypes: [
      {
        type: 'table',
        component: dataTableVisualization,
        displayName: 'Data Table',
      },
      {
        type: 'map',
        displayName: 'World Map',
        component: mapVisualization,
        config: {
          fromConfig,
          toConfig,
          fields: [],
        },
      },
      {
        type: 'visualizationWithConfig',
        displayName: 'Extra Config Required',
        component: dataTableVisualization,
        config: {
          fromConfig,
          toConfig,
          fields: [
            {
              name: 'mode',
              title: 'Mode',
              type: 'select',
              options: ['onemode', 'anothermode', 'thirdmode'],
              required: false,
            },
            {
              name: 'color',
              title: 'Favorite Color',
              type: 'select',
              options: ['red', ['Yellow', 'green'], 'blue'],
              required: true,
              isShown: (formValues: ExtraConfigSettings) => formValues.mode === 'anothermode',
            },
            {
              name: 'invert',
              title: 'Invert',
              type: 'boolean',
            },
            {
              name: 'factor',
              title: 'Important Factor',
              type: 'numeric',
              required: true,
            },
          ],
        },
      },
      {
        type: 'withoutConfig',
        component: dataTableVisualization,
        displayName: 'Without Config',
      },
    ],
  },
};

const findWidgetConfigSubmitButton = () => screen.findByRole('button', { name: /update preview/i });

const expectSubmitButtonToBeDisabled = async () => {
  const submitButton = await findWidgetConfigSubmitButton();

  expect(submitButton).toBeDisabled();
};

describe('AggregationWizard/Visualizations', () => {
  beforeAll(() => PluginStore.register(visualizationPlugin));

  afterAll(() => PluginStore.unregister(visualizationPlugin));

  it('shows visualization section if it is present', async () => {
    render(<SimpleAggregationWizard />);

    await screen.findByText('Visualization');
    await screen.findByText('Data Table');
  });

  it('allows changing to visualization type without required fields', async () => {
    const onChange = jest.fn();
    render(<SimpleAggregationWizard onChange={onChange} />);

    await selectEvent.chooseOption('Select visualization type', 'Without Config');

    await userEvent.click(await findWidgetConfigSubmitButton());

    await waitFor(() =>
      expect(onChange).toHaveBeenCalledWith(
        expect.objectContaining({ visualization: 'withoutConfig', visualizationConfig: undefined }),
      ),
    );
  });

  it('performs proper validation for required fields', async () => {
    const onChange = jest.fn();
    render(<SimpleAggregationWizard onChange={onChange} />);

    await selectEvent.chooseOption('Select visualization type', 'Extra Config Required');

    await waitFor(async () => {
      await expectSubmitButtonToBeDisabled();
    });

    const factorInput = await screen.findByRole('spinbutton', { name: 'Important Factor' });

    await userEvent.clear(factorInput);
    await userEvent.type(factorInput, '10');

    await waitFor(async () => {
      expect(await findWidgetConfigSubmitButton()).not.toBeDisabled();
    });

    await selectEvent.chooseOption('Select Mode', 'anothermode');

    await waitFor(async () => {
      await expectSubmitButtonToBeDisabled();
    });

    await selectEvent.chooseOption('Select Favorite Color', 'Yellow');

    const submitButton = await findWidgetConfigSubmitButton();

    await waitFor(() => {
      expect(submitButton).not.toBeDisabled();
    });

    await userEvent.click(submitButton);

    await waitFor(() =>
      expect(onChange).toHaveBeenCalledWith(
        expect.objectContaining({
          visualization: 'visualizationWithConfig',
          visualizationConfig: {
            color: 'green',
            factor: 10,
            mode: 'anothermode',
          },
        }),
      ),
    );
  });

  it('should update visualization config when changing config inside visualization', async () => {
    const worldMapConfig = widgetConfig.toBuilder().visualization('map').build();
    const onChange = jest.fn();

    const WorldMap = () => {
      const onVisualizationConfigChange = useContext(OnVisualizationConfigChangeContext);

      return (
        <button type="button" onClick={() => onVisualizationConfigChange({ zoom: 2, centerX: 40, centerY: 50 })}>
          Change Viewport
        </button>
      );
    };

    render(
      <SimpleAggregationWizard onChange={onChange} config={worldMapConfig}>
        <WorldMap />
      </SimpleAggregationWizard>,
    );

    const updateViewportButton = await screen.findByRole('button', { name: 'Change Viewport' });
    await userEvent.click(updateViewportButton);
    const submitButton = await findWidgetConfigSubmitButton();
    await userEvent.click(submitButton);

    await waitFor(() =>
      expect(onChange).toHaveBeenCalledWith(
        expect.objectContaining({
          visualization: 'map',
          visualizationConfig: {
            zoom: 2,
            centerX: 40,
            centerY: 50,
          },
        }),
      ),
    );
  });
});
